/**
* @file led.c
* @brief LED驱动
* @author bingfengxiao(bingfengxiao@163.com)
* @version 1.0
* @date 2012-08-28
*/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/io.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/ioport.h>

#define OK (0)

/** I/O端口的物理地址*/
#define GPFCON_PA 0x56000050
#define GPF4	(0x01 << 4)
#define GPF7	(0x01 << 7)
#define LEDA	GPF4
#define LEDB	GPF7
#define GPIO_MODE_IN 0x00
#define GPIO_MODE_OUT 0x01

#define GPFCON (gpfreg)
#define GPFDATA (GPFCON + 1)
#define GPFUP (GPFCON + 2)

struct resource *gpf_resrc = NULL;
u32 *gpfreg = NULL;
struct cdev led_cdev;
dev_t dev;
struct class *led_class = NULL;
struct device *led_dev = NULL;

/**
* @brief write_reg 
*
* @param reg
* @param data
*/
void write_reg(u32 *reg, u32 data){
	iowrite32(data, reg);
}

/**
* @brief set_gpio_ctl 
*/
void set_gpio_ctl(void){
	u32 tmp;
	/** GPF4,7用作输出口*/
	tmp = ioread32(GPFCON);
	tmp &= ~((GPIO_MODE_OUT << 8) | (GPIO_MODE_OUT << 14));
	tmp |= (GPIO_MODE_OUT << 8) | (GPIO_MODE_OUT << 14);
	iowrite32(tmp, GPFCON);

	/** GPF4,7上拉电阻使能*/
	tmp = ioread32(GPFUP);
	tmp &= ~((0x01 << 4) | (0x01 << 7));
	iowrite32(tmp, GPFUP);

	/** 关LED*/
	tmp = ioread32(GPFDATA);
	tmp |= 0x90;
	iowrite32(tmp, GPFDATA);
	
	return;
	
}

/**
* @brief led_open 
*
* @param node
* @param fp
*
* @return 
*/
int led_open(struct inode *node, struct file *fp){
	printk("Led open\n");
	return OK;
}

/**
* @brief led_release 
*
* @param node
* @param fp
*
* @return 
*/
int led_release(struct inode *node, struct file *fp){
	printk("Led release\n");
	return OK;
}

/**
* @brief led_write 
*
* @param fp
* @param data
* @param len
* @param offset
*
* @return 
*/
ssize_t led_write(struct file *fp, const char __user *data, size_t len, loff_t * offset){
	char *p;
	u32 tmp;
	unsigned long reti;
	unsigned long leddata;

	p = kmalloc(len, GFP_KERNEL);
	if (p == NULL){
		printk("malloc memory faild\n");
		return -ENOBUFS;
	}

	reti = copy_from_user(p, data, len);
	if (reti >= len){
		printk("Copy from user faild\n");
		return -ENOBUFS;
	}
	
	if (kstrtoul(data, 16, &leddata) < 0){
		printk("Write Data error!\n");
		return -ENOBUFS;
	}
	printk("data is %s len = %d leddata = %02X\n", p, len, (u8)leddata);
	
	tmp = ioread32(GPFDATA);	
	tmp &= ~(LEDA | LEDB);
	tmp |= ( leddata & (LEDA | LEDB));
	iowrite32(tmp, GPFDATA);
	kfree(p);
	return len;
}



struct file_operations test_led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.write = led_write,
	.release = led_release,
};

/**
* @brief led_init 
*
* @return 
*/
static int __init led_init(void){

	int reti;
	
	printk("LED: Module init!\n");

	gpf_resrc = request_mem_region(GPFCON_PA, 3 * sizeof(int), "test_resource");
	if (gpf_resrc == NULL){
		printk("request mem region  failed!\n");
		return -ENOBUFS;
	}
	
	GPFCON = (u32 *)ioremap_nocache(gpf_resrc->start, gpf_resrc->end - gpf_resrc->start);
	if (GPFCON == NULL){
		release_mem_region(gpf_resrc->start, gpf_resrc->end - gpf_resrc->start);
		gpf_resrc = NULL;
		printk("Get ioremap failed!\n");
		return -ENOBUFS;
	}

	set_gpio_ctl();	

	reti = alloc_chrdev_region(&dev, 0, 1, "test_led");
	printk("alloc_chrdev_region retrun is %d\n", reti);
	/** 不确定alloc_chrdev_region的返回值是什么*/
	if (reti < 0){
		printk("Alloc char device id failed!\n");
		iounmap(GPFCON);
		release_mem_region(gpf_resrc->start, gpf_resrc->end - gpf_resrc->start);
		gpf_resrc = NULL;
		return -ENOBUFS;
	}

	printk("Alloc char device id ok major = %d, MINOR = %d\n", MAJOR(dev), MINOR(dev));

#if 0
	led_cdev = cdev_alloc();
	if (led_cdev == NULL){
		printk("Alloc char device failed!\n");
		iounmap(GPFCON);
		unregister_chrdev_region(dev, 1);
		return -ENOBUFS;
	}
	led_cdev->ops = &test_led_fops;
#endif
	cdev_init(&led_cdev, &test_led_fops);
	led_cdev.owner = THIS_MODULE;

	if (cdev_add(&led_cdev, dev, 1) < 0){
		printk("Add cdev failed!\n");
		unregister_chrdev_region(dev, 1);
		iounmap(GPFCON);
		release_mem_region(gpf_resrc->start, gpf_resrc->end - gpf_resrc->start);
		gpf_resrc = NULL;
		return -ENOBUFS;
	}	

	led_class = class_create(THIS_MODULE, "test-led");
	if (led_class == NULL){
		printk("Create led class faild!\n");
		cdev_del(&led_cdev);
		unregister_chrdev_region(dev,1);
		iounmap(GPFCON);
		release_mem_region(gpf_resrc->start, gpf_resrc->end - gpf_resrc->start);
		gpf_resrc = NULL;
		return -ENOBUFS;
	}

	led_dev = device_create(led_class, NULL, dev, NULL, "test-led");
	if (led_dev == NULL){
		printk("Create device faild!\n");
		class_destroy(led_class);
		cdev_del(&led_cdev);
		unregister_chrdev_region(dev,1);
		iounmap(GPFCON);
		release_mem_region(gpf_resrc->start, gpf_resrc->end - gpf_resrc->start);
		gpf_resrc = NULL;
		return -ENOBUFS;
	}
	
	printk("LED: Module init OK!\n");
	return OK;
}
module_init(led_init);

/**
* @brief led_exit 
*
* @return 
*/
static void __exit led_exit(void){
	printk("LED: Module exit!\n");
	device_destroy(led_class, dev);
	class_destroy(led_class);
	cdev_del(&led_cdev);
	unregister_chrdev_region(dev, 1);
	iounmap(GPFCON);
	release_mem_region(gpf_resrc->start, gpf_resrc->end - gpf_resrc->start);
	gpf_resrc = NULL;

	return;

}
module_exit(led_exit);

MODULE_LICENSE("GPL");
